﻿using System;
using System.Collections.Generic;
using System.Xml.Linq;
using System.ServiceModel.Syndication;
using NCMIS.ObjectModel.MetaData;
using NCMIS.Provider;
using NCMIS.ObjectModel.Rest;
using NCMIS.Produce;

namespace NCMIS.ServiceModel.Syndication
{
    /// <summary>
    /// Represents a CMIS workspace element, extended with Atom Extensions for CMIS, within a CMIS service document.
    /// </summary>
    public class CmisWorkspace : Workspace
    {
        /// <summary>
        /// Gets or sets the RepositoryId associated with this instance.
        /// </summary>
        public string RepositoryId
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the <see cref="NCMIS.Produce.RepositoryInfo"/> object associated with this instance.
        /// </summary>
        public RepositoryInfo RepositoryInfo
        {
            get;
            set;
        }

        /// <summary>
        /// Creates a new instance of the <see cref="NCMIS.ServiceModel.Syndication.CmisWorkspace"/> class.
        /// </summary>
        public CmisWorkspace(string title, IEnumerable<ResourceCollectionInfo> collections)
            : base(title, collections)
        {
            // Nothing
        }

        /// <summary>
        /// Creates a new instance of the <see cref="NCMIS.ServiceModel.Syndication.CmisWorkspace"/> class.
        /// </summary>
        /// <param name="repositoryId">The repository id.</param>
        /// <param name="title">The workspace title.</param>
        /// <param name="baseUri">The workspace base URI.</param>
        /// <param name="repositoryInfo">The RepositoryInfo object associated with this workspace.</param>
        public CmisWorkspace(string repositoryId, string title, Uri baseUri, RepositoryInfo repositoryInfo)
            : base()
        {
            this.RepositoryId = repositoryId;
            this.Title = new TextSyndicationContent(title);
            this.BaseUri = baseUri;
            this.RepositoryInfo = repositoryInfo;

            this.AddCollections();
            this.AddLinks();
            this.AddRepositoryInfo();
            this.AddUriTemplate();
        }

        #region SyndicationLinks

        /// <summary>
        /// Adds the workspace links to workspace element.
        /// </summary>
        public void AddLinks()
        {
            //
            // The workspace element holds four link relations to feeds (one required, and three conditionally required):
            //
            // + Type Descendants Feed Link (Required)
            // + Folder Tree Feed Link (Conditionally required, if service is supported)
            // + Root Descendants Feed Link (Conditionally required, if service is supported)
            // + Changes Feed (Conditionally required, if service is supported)
            //

            //
            // The System.ServiceModel.Syndication.Workspace class does not contain a links collection. This is a "hack" for
            // adding the links collection to the current Workspace instance (using ElementExtensions). The link element
            // will be placed in the Atom namespace.
            //

            XNamespace atomns = CmisNs.Atom;
            //
            // Type Descendants Feed Link (Required)
            //
            XAttribute[] typeDescendantsAttributes = new XAttribute[]
            {
                new XAttribute("title", "Type Descendants Feed"),
                new XAttribute("type", CmisContentType.Tree),
                new XAttribute("rel", CmisLinkRel.TypeDescendants),
                new XAttribute("href", String.Format("{0}{1}/typedescendants/{2}", BaseUri.AbsoluteUri, RepositoryId, "baseType")),
            };
            this.ElementExtensions.Add(new XElement(atomns + "link", typeDescendantsAttributes, String.Empty));
            //
            // Folder Tree Feed Link (Conditionally required, if service is supported)
            //
            if (this.RepositoryInfo.Capabilities.GetFolderTree == true)
            {
                XAttribute[] folderTreeAttributes = new XAttribute[]
                {
                    new XAttribute("title", "Folder Tree feed for root folder"),
                    new XAttribute("type", CmisContentType.Tree),
                    new XAttribute("rel", CmisLinkRel.FolderTree),
                    new XAttribute("href", String.Format("{0}{1}/foldertree/?folderId={2}", CmisRestAtom.ServiceUri, RepositoryId, "rootFolder")),
                };
                this.ElementExtensions.Add(new XElement(atomns + "link", folderTreeAttributes, String.Empty));
            }
            //
            // Root Folder Descendants Feed Link (Conditionally required, if service is supported)
            //
            if (this.RepositoryInfo.Capabilities.GetDescendants == true)
            {
                XAttribute[] rootDescendantsAttributes = new XAttribute[]
                {
                    new XAttribute("title", "Root Descendants Feed"),
                    new XAttribute("type", CmisContentType.Tree),
                    new XAttribute("rel", CmisLinkRel.RootDescendants),
                    new XAttribute("href", String.Format("{0}{1}/descendants/{2}", CmisRestAtom.ServiceUri, RepositoryId, "rootFolder")),
                };
                this.ElementExtensions.Add(new XElement(atomns + "link", rootDescendantsAttributes, String.Empty));
            }
            //
            // Changes Feed Link (Required, if service is supported)
            //
            if (this.RepositoryInfo.Capabilities.Changes != CapabilityChanges.None)
            {
                XAttribute[] changesAttributes = new XAttribute[]
                {
                    new XAttribute("title", "Changes Feed"),
                    new XAttribute("type", CmisContentType.Feed),
                    new XAttribute("rel", CmisLinkRel.Changes),
                    new XAttribute("href", String.Format("{0}{1}/changes", CmisRestAtom.ServiceUri, RepositoryId)),
                };
                this.ElementExtensions.Add(new XElement(atomns + "link", changesAttributes, String.Empty));
            }
        }

        #endregion

        #region Atom Extensions

        /// <summary>
        /// Adds cmisra:repositoryInfo element extension.
        /// </summary>
        public void AddRepositoryInfo()
        {
            // RepositoryInfo (extension)
            this.ElementExtensions.Add("repositoryInfo", CmisNs.Cmisra, this.RepositoryInfo);
        }

        /// <summary>
        /// Adds cmisra:uritemplate element extension.
        /// </summary>
        public void AddUriTemplate()
        {
            CmisUriTemplate[] uriTemplates = this.GetUriTemplates();
            foreach (CmisUriTemplate uriTemplate in uriTemplates)
            {
                this.ElementExtensions.Add("uritemplate", CmisNs.Cmisra, uriTemplate);
            }
        }

        /// <summary>
        /// Gets the CMIS URI Templates for current repository.
        /// </summary>
        /// <returns>Collection of <see cref="NCMIS.ObjectModel.Rest.CmisUriTemplate"/> objects.</returns>
        private CmisUriTemplate[] GetUriTemplates()
        {
            string objectByIdTemplate = CmisUriTemplate.ObjectById.Replace("{repositoryId}", this.RepositoryId);
            string objectByPathTemplate = CmisUriTemplate.ObjectByPath.Replace("{repositoryId}", this.RepositoryId);
            string typeByIdTemplate = CmisUriTemplate.TypeById.Replace("{repositoryId}", this.RepositoryId);
            string queryTemplate = CmisUriTemplate.Query.Replace("{repositoryId}", this.RepositoryId);

            CmisUriTemplate[] cmisUriTemplates = new CmisUriTemplate[]
            {
                // TODO: Maps the non-existing ObjectService.GetObjectById (?) method. (Required)
                new CmisUriTemplate()
                {
                    Template = string.Format("{0}{1}", this.BaseUri, objectByIdTemplate),
                    Type = "objectbyid",
                    MediaType = CmisContentType.Entry
                },
                // Maps the URiTemplate of the ObjectService.GetObjectByPath method. (Required)
                new CmisUriTemplate()
                {
                    Template = string.Format("{0}{1}", this.BaseUri, objectByPathTemplate),
                    Type = "objectbypath",
                    MediaType = CmisContentType.Entry
                },
                // Maps the UriTemplate of the RepositoryService.GetTypeDefinition method. (Required)
                new CmisUriTemplate()
                {
                    Template = string.Format("{0}{1}", this.BaseUri, typeByIdTemplate),
                    Type = "typebyid",
                    MediaType = CmisContentType.Entry
                }
            };

            // The Query URI Template will only be added if the current repository supports queries.
            if (this.RepositoryInfo.Capabilities.Query != CapabilityQuery.None)
            {
                // Maps the UriTemplate of the DiscoveryService.Query method. (Optional)
                CmisUriTemplate cmisQueryUriTemplate = new CmisUriTemplate()
                {
                    Template = string.Format("{0}{1}", this.BaseUri, queryTemplate),
                    Type = "query",
                    MediaType = CmisContentType.Entry
                };

                Array.Resize<CmisUriTemplate>(ref cmisUriTemplates, cmisUriTemplates.Length + 1);
                Array.Copy(new CmisUriTemplate[] { cmisQueryUriTemplate }, 0, cmisUriTemplates, cmisUriTemplates.Length - 1, 1);
            }

            return cmisUriTemplates;
        }

        #endregion

        #region Collections

        /// <summary>
        /// Adds the CMIS collection elements to the workspace element. The required collections: Root Folder Collection and Types Children Collection
        /// are always added. The conditionally required collections: Query Collection, Checked Out Collection, and Unfiled Collection will be added
        /// if the service is supported. The <see cref="NCMIS.Produce.RepositoryInfo"/> property specifies which services are supported, specified by its
        /// <see cref="NCMIS.Produce.RepositoryInfo.Capabilities"/> property.
        /// </summary>
        public void AddCollections()
        {
            //
            // Root Folder Collection (Required)
            //
            ResourceCollectionInfo rootcCollection = new ResourceCollectionInfo("Root folder collection", new Uri(CmisRestAtom.ServiceUri, String.Format("{0}/children/{1}", RepositoryId, RepositoryInfo.RootFolderId)));
            rootcCollection.ElementExtensions.Add("collectionType", CmisNs.Cmisra, "root");
            this.Collections.Add(rootcCollection);
            //
            // Query Collection (Conditionally required: Must be emitted if the query functionality is available for the current repository.)
            //
            if (this.RepositoryInfo.Capabilities.Query != CapabilityQuery.None)
            {
                ResourceCollectionInfo queryCollection = new ResourceCollectionInfo("Query collection", new Uri(CmisRestAtom.ServiceUri, String.Format("{0}/query", RepositoryId)));
                queryCollection.Accepts.Add(CmisContentType.Query);
                queryCollection.ElementExtensions.Add("collectionType", CmisNs.Cmisra, "query");
                this.Collections.Add(queryCollection);
            }
            //
            // Checked Out Collection (Conditionally required: Must be emitted if the query functionality is available for the current repository.)
            //
            if (true) // TODO: Which capability does this collection correspond to?
            {
                ResourceCollectionInfo checkedOutCollection = new ResourceCollectionInfo("Checkedout collection", new Uri(CmisRestAtom.ServiceUri, String.Format("{0}/checkedout", RepositoryId)));
                checkedOutCollection.Accepts.Add(CmisContentType.Entry);
                checkedOutCollection.ElementExtensions.Add("collectionType", CmisNs.Cmisra, "checkedout");
                this.Collections.Add(checkedOutCollection);
            }
            //
            // Unfiled Collection (Conditionally required: Must be emitted if the query functionality is available for the current repository.)
            //
            if (this.RepositoryInfo.Capabilities.Unfiling == true)
            {
                ResourceCollectionInfo unfiledCollection = new ResourceCollectionInfo("Unfiled collection", new Uri(CmisRestAtom.ServiceUri, String.Format("{0}/unfiled", RepositoryId)));
                unfiledCollection.Accepts.Add(CmisContentType.Entry);
                unfiledCollection.ElementExtensions.Add("collectionType", CmisNs.Cmisra, "unfiled");
                this.Collections.Add(unfiledCollection);
            }
            //
            // Types Children Collection (Required)
            //
            ResourceCollectionInfo typecCollection = new ResourceCollectionInfo("Types children collection", new Uri(CmisRestAtom.ServiceUri, String.Format("{0}/types", RepositoryId)));
            typecCollection.ElementExtensions.Add("collectionType", CmisNs.Cmisra, "types");
            this.Collections.Add(typecCollection);
        }

        #endregion
    }
}
